#include "../World/World.h"
#include "Graphic.h"
#include "../Log/Log.h"
#include "../System/Configuration.h"
#include "load_png.h"

int WINDOW_WIDTH;
int WINDOW_HEIGHT;

bool Graphic_BorderOff = 1;
int Graphic_Width;
int Graphic_Height;
int Graphic_FlipX;
int Graphic_FlipY;
bool Graphic_bFixed;
float Graphic_Zoom = 1.5f;
int Graphic_Tile;

#define Graphic_MaxTile 128
#define Graphic_MaxFrame 16
#define Graphic_MaxIcon 32

/* SDL */
SDL_Window *Window;
SDL_Renderer *Screen;
SDL_Event Event;
SDL_TimerID G_Timer;
SDL_Thread *T;

G_Input Input;
G_Camera Camera;
G_Mouse Mouse;
G_TimerCount TimerCount;
G_TextureImage *Texture;

int Graphic_LoadConfig() {
    int i;
    /* Lade die Werte */
    WINDOW_WIDTH = Configuration_GetIni( "graphic:width", 640);
    WINDOW_HEIGHT = Configuration_GetIni( "graphic:height", 360);
    i = Configuration_GetIni( "graphic:zoom", 1);
    if(i) {
        WINDOW_WIDTH *= i;
        WINDOW_HEIGHT *= i;
    }
    return 0 ;
}

/** Starte SDL */
int Graphic_Init() {
    /* SDL init */
    SDL_Init(SDL_INIT_EVERYTHING);

    /* Timer  */
    G_Timer = SDL_AddTimer (250, (void *)Graphic_Timer, NULL);

    /* Einstlungen laden */
    Graphic_LoadConfig();

    /* Erstelle ein Fenster */
    Window = SDL_CreateWindow("Pyrus",
                              50,
                              50, //SDL_WINDOWPOS_UNDEFINED,
                              WINDOW_WIDTH, WINDOW_HEIGHT,
                              SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN| SDL_WINDOW_RESIZABLE);
    if (!Window) {
        logError("Window not initialized: %s", SDL_GetError());
        return 0;
    } else {
        /* Create context */
        Screen = SDL_GL_CreateContext( Window );
        if( Screen == NULL) {
            logError( "OpenGL context could not be created! SDL Error: %s", SDL_GetError() );
            return 0;
        } else {
            /* Sync mit Bildschirm */
            if( SDL_GL_SetSwapInterval( 1 ) < 0 ) {
                logWarning( "Unable to set VSync! SDL Error: %s", SDL_GetError() );
            }
            /* Initialize OpenGL */
            if( !Graphic_InitGL() ) {
                logError( "Unable to initialize OpenGL!" );
                return 0;
            }
        }
    }

    /* Lade die Daten */
    if(Graphic_LoadFiles() == 0) {
        logError("Unable to load the Graphic Files!");
        return 0;
    }

    /* was unterstützt wird */
    logDebug("OpenGL version supported by this platform (%s):", glGetString(GL_VERSION));


    /* Camera */
    Camera.X = 0;
    Camera.Y = 0;
    Camera.W = 640;
    Camera.H = 360;
    Camera.MaxW = WINDOW_WIDTH;
    Camera.MaxH = WINDOW_HEIGHT;
    /* Zoom */
    Graphic_CalcZoom();

    /* Input */
    Input.Quit = 0;

    return 1;
}

/* OpenGL zusammenkratzen */
int Graphic_InitGL() {
    /* Viewport und die 2D Ansicht einstellen */
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glViewport(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
    glOrtho(0, WINDOW_WIDTH, WINDOW_HEIGHT, 0, 24, -1);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    /* Einstellungen 2D */
    glShadeModel(GL_FLAT);
    //glDisable(GL_CULL_FACE);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_BLEND);
    glEnable(GL_ALPHA_TEST);
    glAlphaFunc(GL_GREATER, 0.1);
    glBlendFunc(GL_ONE, GL_ZERO);

    /* Enable the texture for OpenGL. */
    glEnable(GL_TEXTURE_2D);
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

	/* Alte Datei */
	glLoadIdentity();

    if(glGetError() != GL_NO_ERROR) {
        logError("glGetError %d\n ", glGetError());
        return 0;
    }

	return 1;
}

/** Image laden */
G_TextureImage Graphic_LoadImage(char *filename) {
    int w, h;
    G_TextureImage Tex;
    GLuint tex;

    /* Pixeldaten speicher einstellen */
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

	/* Textur erstellen */
	glGenTextures(1, &tex);
	glBindTexture(GL_TEXTURE_2D, tex);

	/* Textur Pixelgenau machen */
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);//GL_CLAMP_TO_EDGE
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

	/* Datei öffnen und lesen */
	GLubyte *pixels = (GLubyte *)load_png(filename, &w, &h);
	/* Als textur(opengl) speichern */
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
	free(pixels);

	/* Als structur speichern */
	Tex.height = h;
	Tex.width = w;
	Tex.imageData = (GLuint *)tex;

	return Tex;
}

/* Berechnungen die ihre zeit brauchen wie effekte oder bewegungen */
int Graphic_Timer(Uint32 intervall, void *parameter) {
    TimerCount.Count += 1;
    return intervall;
}


/** Camera */
float Graphic_GetCamera( int XYorWH) {
    switch(XYorWH) {
        case 0: return (float)Camera.X; break;
        case 1: return (float)Camera.Y; break;
        case 2: return (float)Camera.W; break;
        case 3: return (float)Camera.H; break;
        default: return 0;
    }
}

void Graphic_Camera( int X, int Y) {
    Camera.MoveX = X-(Camera.W/2);
    Camera.MoveY = Y-(Camera.H/2);
}

void Graphic_Border() {
    /* Eintellung Grenzen aus */
    if(Graphic_BorderOff)
        return;
    logDebug("test");
    /* Nicht über die ränder anzeigen */
    if(Camera.X <= 0)
        Camera.X = 0;
    /*if(Camera.Y <= 0)
        Camera.Y = 0;*/
    if(Camera.X >= Camera.MaxW-Camera.W)
        Camera.X = Camera.MaxW-Camera.W;
    if(Camera.Y >= Camera.MaxH-Camera.H)
        Camera.Y = Camera.MaxH-Camera.H;
}

void Graphic_SetCamera(int maxW, int maxH) {
    /* Setze die Grenzen */
    Camera.MaxW = maxW;
    Camera.MaxH = maxH;
    /* Y in die mitte setzen */
    Camera.Y = Camera.MaxH/2 - Camera.H/2;
}

void Graphic_CentralCamera(int W, int H)
{
    Camera.X = W*30-(Camera.W/2);
    Camera.Y = H*15-(Camera.H/2);
    if(Camera.Y < 0)
        Camera.Y = 0;
    if(Camera.X < 0)
        Camera.X = 0;
}

void Graphic_MoveCamera() {
    int X, Y, Move = false;
    double Faktor = 0.1;
    /* Langsam zum Ziel gleiten */
    if(Camera.MoveX) {
        X = (float)Camera.MoveX - Camera.X;
        Camera.X += ((double)X*Faktor);
        Move = true;
    }
    if(Camera.MoveY) {
        Y = (float)Camera.MoveY - Camera.Y;
        Camera.Y += ((double)Y*Faktor);
        Move = true;
    }
    /* Falls sich die Camere bewegt hat -> grenzen beachten */
    if(Move)
        Graphic_Border();
}

int Graphic_Distance(int X, int Y, int X2, int Y2) {
    int Distx;
    int Disty;
    /* Welchen rechenweg soll genohmen werden */
    switch(3) {
        case 0:
            if ( X >= X2 )  Distx = X-X2;
            else Distx = X2-X;
            if (Y >= Y2)  Disty = Y-Y2;
            else  Disty = Y2-Y;
            return sqrt( (Distx*Distx) + (Disty*Disty) );
        break;
        case 1:
            return sqrt( (X-X2)*(X-X2)+(Y-Y2)*(Y-Y2));
        break;
        case 2:
            return sqrt( pow((X-X2), 2) + pow((Y-Y2), 2) );
        break;
        case 3:
            Distx = X2-X;
            Disty = Y2-Y;
            return sqrt( Distx*Distx + Disty*Disty);
        break;
    }
}

void Graphic_Input() {
    /* Check for events */
    while (SDL_PollEvent(&Event)) {
        if(SDL_GetMouseState((int *)&Mouse.X, (int *)&Mouse.Y))
        Input.X = Mouse.X/Graphic_Zoom;
        Input.Y = Mouse.Y/Graphic_Zoom;

        if(SDL_GetMouseState(NULL, NULL)&SDL_BUTTON(1)) {
            Input.Mouse_1 = 1;
        }
        if(SDL_GetMouseState(NULL, NULL)&SDL_BUTTON(2)) {
            Input.Mouse_2 = 1;
        }

        if(SDL_GetMouseState(NULL, NULL)&SDL_BUTTON(3)) {
            Input.Mouse_3 = 1;
        }

        switch (Event.type)
        {
            case SDL_QUIT: Input.Quit = 1; break;
            case SDL_KEYDOWN:
                switch( Event.key.keysym.sym )
                {
                    case SDLK_UP:       Input.Up    = 1; break;
                    case SDLK_DOWN:     Input.Down  = 1; break;
                    case SDLK_RIGHT:    Input.Right = 1; break;
                    case SDLK_LEFT:     Input.Left  = 1; break;
                }
            break;
            case SDL_WINDOWEVENT:
                if(Event.window.event == SDL_WINDOWEVENT_RESIZED) {
                    /* Jetztige größe auslesen */
                    WINDOW_WIDTH = Event.window.data1;
                    WINDOW_HEIGHT = Event.window.data2;
                    Graphic_InitGL();
                    /* Zoom */
                    Graphic_CalcZoom();
                    //Graphic_InitGL();
                }
            break;
        }
    }
    return;
}

void Graphic_ToggleFullscreen() {
    if(SDL_GetWindowFlags(Window) & SDL_WINDOW_FULLSCREEN)
        SDL_SetWindowFullscreen(Window, 0);
    else
        SDL_SetWindowFullscreen(Window, SDL_WINDOW_FULLSCREEN);
}

void Graphic_CalcZoom() {
    /* berechne zoom */
    //if(WINDOW_WIDTH-(Camera.W/2) < WINDOW_HEIGHT)
        Graphic_Zoom = (float)WINDOW_WIDTH/Camera.W;
    /*else {
        Graphic_Zoom = (float)WINDOW_HEIGHT/Camera.H;
    }*/
    /* Log */
    logDebug("Set the Zoom to %.3f", Graphic_Zoom);
}

int Graphic_Process()
{
    Graphic_Input();
    /* Anscheinend frisst es CPU wenn sie NICHT genützt wird daher werd ich eine kleine "pause" machen*/
    /* 1/framen*1000 = MaxMS */
    /* 1/60fps = 16,6 */
    /* Wird später behoben */
    SDL_Delay(12);
    /* Camera langsam bewegen bewegen */
    Graphic_MoveCamera();
    /* Auf den Bildschirm */
    SDL_GL_SwapWindow( Window );
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	/* Alte Datei */
	glLoadIdentity();

    /* Überlauf schutz */
    if( TimerCount.Count > 100 )
        TimerCount.Count -= 100;
    return (!Input.Quit);
}

void Graphic_ApplySurfaceRaw(float X, float Y, int W, int H, float Z) {
    glPushMatrix();
    /* Achsen setzten */
    glTranslatef( X*Graphic_Zoom, Y*Graphic_Zoom, 0);
    glScalef(Graphic_Zoom,Graphic_Zoom,1);
    /* Textur */
	glBegin(GL_QUADS);
        glTexCoord2f(0.0, 0.0); glVertex3f(0, H, Z);
        glTexCoord2f(0.0, 1.0); glVertex3f(0, 0, Z);
        glTexCoord2f(1.0, 1.0); glVertex3f(W, 0, Z);
        glTexCoord2f(1.0, 0.0); glVertex3f(W, H, Z);
	glEnd();
	glPopMatrix();
}

void Graphic_Draw(float X, float Y, int Layer, int Zentrieren) {
    float rX, rY, rZ;
    int rW, rH;
    /* Umrechnen */
    rX = X;
    rY = Y;
    /* Camera */
    if(!Graphic_bFixed) {
        rX -= Camera.X;
        rY -= Camera.Y;
    }
    /* Layer */
    rZ = -Layer;
    /* Eingestellte Größe */
    if(Graphic_Width)
        rW = Graphic_Width;
    else rW = Texture->width;
    if(Graphic_Height)
        rH = Graphic_Height;
    else rH = Texture->height;
    /* Zentrieren */
    if(Zentrieren) {
        rX -= rW/2;
        rY -= rH/2;
    }
    /* nicht über rand zeichnen */
    if(rX < 0-(rW)  || rY < 0-(rH))
        return;
    if(rX-rW > Camera.W+rW || rY-rW > Camera.H+rH)
        return;
    /* Blit */
    Graphic_ApplySurfaceRaw( rX, rY, rW, rH, rZ);
}

void Graphic_SetRect(int W, int H) {
    Graphic_Width = W;
    Graphic_Height = H;
}

void Graphic_Flip(bool X, bool Y) {
    if( X)
        Graphic_FlipX = true;
    if( Y)
        Graphic_FlipY = true;
}

void Graphic_Fixed() {
    Graphic_bFixed = true;
}

/**                 */
/** Game inhalte    */
/**                 */

G_TextureImage Tile[128];
G_TextureImage Player[15];
G_TextureImage Frame[Graphic_MaxFrame];
G_TextureImage Background[Graphic_MaxBackground];
G_TextureImage Icon[Graphic_MaxIcon];

SDL_Rect rPlayer[16];

/* Laden der Daten */
int Graphic_LoadFiles() {
    char sFile[256];
    int i;
    FILE *File;
    /* Player */
    for( i = 0; i < 3; i++) {
        sprintf (sFile, "player/walk%d.png", i);
        Player[i] = Graphic_LoadImage(sFile);
    }
    /* Player */
    for( i = 0; i < 3; i++) {
        sprintf (sFile, "player/idle%d.png", i);
        Player[i+3] = Graphic_LoadImage(sFile);
    }
    /* Tiles laden */
    for( i = 1; i < Graphic_MaxTile; i++) {
        sprintf (sFile, "tileset/%d.png", i);
        /* Checken ob die datei vorhanden ist */
        File = fopen(sFile, "r");
        if(File == NULL) {
            Graphic_Tile = i;
            break;
        }
        Tile[i] = Graphic_LoadImage(sFile);
    }
    /* Frame laden */
    for( i = 0; i < Graphic_MaxFrame; i++) {
        sprintf (sFile, "other/frame%d.png", i);
        /* Checken ob die datei vorhanden ist */
        File = fopen(sFile, "r");
        if(File == NULL) {
            break;
        }
        fclose(File);
        Frame[i] = Graphic_LoadImage(sFile);
    }
    /* Background laden */
    for( i = 0; i < Graphic_MaxBackground; i++) {
        sprintf (sFile, "background/background-%d.png", i+1);
        /* Checken ob die datei vorhanden ist */
        File = fopen(sFile, "r");
        if(File == NULL) {
            break;
        }
        Background[i] = Graphic_LoadImage(sFile);
    }
    /* Icon laden */
    for( i = 0; i < Graphic_MaxIcon; i++) {
        sprintf (sFile, "other/icon%d.png", i);
        /* Checken ob die datei vorhanden ist */
        File = fopen(sFile, "r");
        if(File == NULL) {
            break;
        }
        Icon[i] = Graphic_LoadImage(sFile);
    }
    return 1;
}

void Graphic_Reset() {
    Graphic_Width = 0;
    Graphic_Height = 0;
    Graphic_FlipX = 0;
    Graphic_FlipY = 0;
    Graphic_bFixed = false;
}

void Graphic_LoadTile(int Index) {
    glBindTexture (GL_TEXTURE_2D, (int)Tile[Index].imageData);
    Texture = &Tile[Index];
    Graphic_Reset();
}

void Graphic_LoadIcon(int Index) {
    glBindTexture (GL_TEXTURE_2D, (int)Icon[Index].imageData);
    Texture = &Icon[Index];
    Graphic_Reset();
}

void Graphic_LoadFrame(int Index) {
    glBindTexture (GL_TEXTURE_2D, (int)Frame[Index].imageData);
    Texture = &Frame[Index];
    Graphic_Reset();
}

void Graphic_LoadBackground(int Index) {
    /* Absichern */
    if(Index >= Graphic_MaxBackground)
        Index = Graphic_MaxBackground-1;
    glBindTexture (GL_TEXTURE_2D, (int)Background[Index].imageData);
    Texture = &Background[Index];
    Graphic_Reset();
}

void Graphic_LoadPlayer(int Index, int Frame) {
    glBindTexture (GL_TEXTURE_2D, (int)Player[(Index*3)+Frame].imageData);
    Texture = &Player[(Index*3)+Frame];
    Graphic_Reset();
}

void Graphic_Background( int iBackground) {
    int X, Y, n, i;
    X = 0;
    Y = 0;
    /* Textur laden */
    Graphic_LoadBackground(iBackground);
    /* */
    n = Camera.W/Texture->width+1;
    while(Camera.X-X > Texture->width)
        X += Texture->width;
    /* Anzeigen */
    for(i = 0; i < n+1; i++)
        Graphic_Draw( X+(Texture->width*i), Y, 1, false);
}

void Graphic_Quit()
{
    //SDL_KillThread(T_Input);
    SDL_GL_DeleteContext(Screen);
    SDL_DestroyWindow(Window);
    SDL_Quit();
}
